#!/usr/bin/env python
#
# Copyright (C) 2011 Clement Lorteau <clem@lorteau.fr>	
#		
# This file is part of QuickScreenShots.		
# 	
# This program is free software; you can redistribute it and/or modify	
# it under the terms of the GNU General Public License as published by		
# the Free Software Foundation; version 3.	
# 	
# This program is distributed in the hope that it will be useful,		
# but WITHOUT ANY WARRANTY; without even the implied warranty of		
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the		
# GNU General Public License for more details.		
# 		
# You should have received a copy of the GNU General Public License		
# along with this program; if not, write to the Free Software		
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

import wx
import wx.lib.ogl as ogl
from PIL import Image, ImageFilter

"""
Shamelessly copied from wxpython pySketch sample, written by Erik Westra and Bill Baxter.

I only added the arrow and blurring stuff.
"""

def Init():
    ogl.OGLInitialize()


class DrawingTool(object):
    """Base class for drawing tools"""

    def __init__(self):
        pass

    def getDefaultCursor(self):
        """Return the cursor to use by default which this drawing tool is selected"""
        return wx.STANDARD_CURSOR

    def draw(self,dc):
        pass


    def onMouseEvent(self,parent, event):
        """Mouse events passed in from the parent.

        Returns True if the event is handled by the tool,
        False if the canvas can try to use it.
        """
        event.Skip()
        return False

class LineDrawingTool(DrawingTool):
    """Represents the tool for drawing lines"""

    def __init__(self):
        self.newObject = None
        self.startPt = None


    def getDefaultCursor(self):
        """Return the cursor to use by default which this drawing tool is selected"""
        return wx.StockCursor(wx.CURSOR_PENCIL)

    def draw(self, dc):
        if self.newObject is None: return
        self.newObject.draw(dc,True)

    def onMouseEvent(self,parent, event):
        handlers = { wx.EVT_LEFT_DOWN.evtType[0]: self.onMouseLeftDown,
                     wx.EVT_MOTION.evtType[0]:    self.onMouseMotion,
                     wx.EVT_LEFT_UP.evtType[0]:   self.onMouseLeftUp }
        handler = handlers.get(event.GetEventType())
        if handler is not None:
            return handler(parent,event)
        else:
            event.Skip()
            return False

    def onMouseLeftDown(self,parent, event):
        self.startPt = wx.Point(event.GetX(), event.GetY())
        self.newObject = None
        event.Skip()
        return True

    def onMouseMotion(self,parent, event):
        if not event.Dragging(): return

        if self.newObject is None:
            obj = LineDrawingObject(startPt=wx.Point(0,0),
                                    penColour=parent.penColour,
                                    fillColour=parent.fillColour,
                                    lineSize=parent.lineSize,
                                    position=wx.Point(event.X,event.Y))
            self.newObject = obj

        self._updateObjFromEvent(self.newObject, event)

        parent.requestRedraw()
        event.Skip()
        return True

    def onMouseLeftUp(self,parent, event):

        if self.newObject is None:
            return

        self._updateObjFromEvent(self.newObject,event)

        parent.addObject(self.newObject)

        self.newObject = None
        self.startPt = None

        event.Skip()
        return True


    def _updateObjFromEvent(self,obj,event):
        obj.setEndPt(wx.Point(event.X,event.Y))
        
class ArrowDrawingTool(LineDrawingTool):

    def __init__(self):
        LineDrawingTool.__init__(self)
        
    def onMouseMotion(self,parent, event):
        if not event.Dragging(): return

        if self.newObject is None:
            obj = ArrowDrawingObject(startPt=wx.Point(0,0),
                                     penColour=parent.penColour,
                                     fillColour=parent.fillColour,
                                     lineSize=parent.lineSize,
                                     position=wx.Point(event.X,event.Y))
            self.newObject = obj

        self._updateObjFromEvent(self.newObject, event)

        parent.requestRedraw()
        event.Skip()
        return True

class RectDrawingTool(DrawingTool):
    """Represents the tool for drawing rectangles"""

    def __init__(self):
        self.newObject = None

    def getDefaultCursor(self):
        """Return the cursor to use by default which this drawing tool is selected"""
        return wx.CROSS_CURSOR

    def draw(self, dc):
        if self.newObject is None: return
        self.newObject.draw(dc,True)


    def onMouseEvent(self,parent, event):
        handlers = { wx.EVT_LEFT_DOWN.evtType[0]: self.onMouseLeftDown,
                     wx.EVT_MOTION.evtType[0]:    self.onMouseMotion,
                     wx.EVT_LEFT_UP.evtType[0]:   self.onMouseLeftUp }
        handler = handlers.get(event.GetEventType())
        if handler is not None:
            return handler(parent,event)
        else:
            event.Skip()
            return False

    def onMouseLeftDown(self,parent, event):
        self.startPt = wx.Point(event.GetX(), event.GetY())
        self.newObject = None
        event.Skip()
        return True

    def onMouseMotion(self,parent, event):
        if not event.Dragging(): return

        if self.newObject is None:
            obj = RectDrawingObject(penColour=parent.penColour,
                                    fillColour=parent.fillColour,
                                    lineSize=parent.lineSize)
            self.newObject = obj

        self._updateObjFromEvent(self.newObject, event)

        parent.requestRedraw()
        event.Skip()
        return True

    def onMouseLeftUp(self,parent, event):

        if self.newObject is None:
            return

        self._updateObjFromEvent(self.newObject,event)

        parent.addObject(self.newObject)

        self.newObject = None

        event.Skip()
        return True


    def _updateObjFromEvent(self,obj,event):
        x = [event.X, self.startPt.x]
        y = [event.Y, self.startPt.y]
        x.sort()
        y.sort()
        width = x[1]-x[0]
        height = y[1]-y[0]

        obj.setPosition(wx.Point(x[0],y[0]))
        obj.setSize(wx.Size(width,height))

class EllipseDrawingTool(DrawingTool):
    """Represents the tool for drawing ellipses"""

    def getDefaultCursor(self):
        """Return the cursor to use by default which this drawing tool is selected"""
        return wx.CROSS_CURSOR


    def __init__(self):
        self.newObject = None

    def getDefaultCursor(self):
        """Return the cursor to use by default which this drawing tool is selected"""
        return wx.CROSS_CURSOR

    def draw(self, dc):
        if self.newObject is None: return
        self.newObject.draw(dc,True)


    def onMouseEvent(self,parent, event):
        handlers = { wx.EVT_LEFT_DOWN.evtType[0]: self.onMouseLeftDown,
                     wx.EVT_MOTION.evtType[0]:    self.onMouseMotion,
                     wx.EVT_LEFT_UP.evtType[0]:   self.onMouseLeftUp }
        handler = handlers.get(event.GetEventType())
        if handler is not None:
            return handler(parent,event)
        else:
            event.Skip()
            return False
            
    def onMouseLeftDown(self,parent, event):
        self.startPt = wx.Point(event.GetX(), event.GetY())
        self.newObject = None
        event.Skip()
        return True

    def onMouseMotion(self,parent, event):
        if not event.Dragging(): return

        if self.newObject is None:
            obj = EllipseDrawingObject(penColour=parent.penColour,
                                       fillColour=parent.fillColour,
                                       lineSize=parent.lineSize)
            self.newObject = obj

        self._updateObjFromEvent(self.newObject, event)

        parent.requestRedraw()
        event.Skip()
        return True 

    def onMouseLeftUp(self,parent, event):

        if self.newObject is None:
            return

        self._updateObjFromEvent(self.newObject,event)

        parent.addObject(self.newObject)

        self.newObject = None

        event.Skip()
        return True


    def _updateObjFromEvent(self,obj,event):
        x = [event.X, self.startPt.x]
        y = [event.Y, self.startPt.y]
        x.sort()
        y.sort()
        width = x[1]-x[0]
        height = y[1]-y[0]

        obj.setPosition(wx.Point(x[0],y[0]))
        obj.setSize(wx.Size(width,height))       

class ScribbleDrawingTool(DrawingTool):
    """Represents the tool for drawing scribble drawing objects"""

    def __init__(self):
        self.newObject = None

    def getDefaultCursor(self):
        """Return the cursor to use by default which this drawing tool is selected"""
        return wx.StockCursor(wx.CURSOR_PENCIL)

    def draw(self, dc):
        if self.newObject is None: return
        self.newObject.draw(dc,True)


    def onMouseEvent(self,parent, event):
        handlers = { wx.EVT_LEFT_DOWN.evtType[0]:  self.onMouseLeftDown,
                     wx.EVT_MOTION.evtType[0]:     self.onMouseMotion,
                     wx.EVT_LEFT_UP.evtType[0]:    self.onMouseLeftUp 
                    }
        handler = handlers.get(event.GetEventType())
        if handler is not None:
            return handler(parent,event)
        else:
            event.Skip()
            return False
            
    def onMouseLeftDown(self,parent, event):
        event.Skip()
        obj = ScribbleDrawingObject(points=[(0,0)],penColour=parent.penColour,
                                    fillColour=parent.fillColour,
                                    lineSize=parent.lineSize,
                                    position=wx.Point(event.X, event.Y))
        self.newObject = obj
        return True

    def onMouseMotion(self,parent, event):
        event.Skip()
        if self.newObject:
            self.newObject.addPoint(event.X,event.Y)
            parent.requestRedraw()
            return True 

        return False

    def onMouseLeftUp(self,parent, event):
        event.Skip()
        if self.newObject:
            parent.addObject(self.newObject)
            self.newObject = None
        return True
        
class BlurDrawingTool(ScribbleDrawingTool):
    def __init__(self):
        ScribbleDrawingTool.__init__(self)
        
    def onMouseLeftDown(self, parent, event):
        """Create the blurred image, pass it to the drawing object that will
        draw on the original image by blitting the blurred image"""
        event.Skip()

        im = parent.GetIM().copy()
        
        #crop im to part viewable in the scrolled window
        # originX, originY = parent.scrolled.GetViewStart()
        # unitX, unitY = parent.scrolled.GetScrollPixelsPerUnit()
        # x = originX * unitX
        # y = originY * unitY
        # sizeScrolled = parent.scrolled.GetClientSize()
        # sizeX = min(x+sizeScrolled[0], parent.previewBitmap.GetSize()[0])
        # sizeY = min(y+sizeScrolled[1], parent.previewBitmap.GetSize()[1])

        # box = (x, y, sizeX, sizeY)
        # bim = im.crop(box)
        bim = im
        # bim.save('prout.jpg')
        
        #blur 3 times
        bim = bim.filter(ImageFilter.BLUR).filter(ImageFilter.BLUR).filter(ImageFilter.BLUR)
        
        #convert im to bmp
        myWxImage = wx.EmptyImage(*bim.size)
        myPilImageCopyRGB = bim.convert('RGB')
        myPilImageRgbData =myPilImageCopyRGB.tostring()
        myWxImage.SetData(myPilImageRgbData)
        bmp = myWxImage.ConvertToBitmap()
        
        #figure out by how many pixels the screenshot was scrolled
        originX, originY = parent.scrolled.GetViewStart()
        unitX, unitY = parent.scrolled.GetScrollPixelsPerUnit()
        x = originX * unitX
        y = originY * unitY
        
        obj = BlurDrawingObject(points=[(0,0)], blurredbmp=bmp, offset=(x,y),
                                lineSize=parent.lineSize,
                                position=wx.Point(event.X, event.Y))
        self.newObject = obj
        return True                        

class DrawingObject(object):
    """ Base class for objects within the drawing panel.

        A pySketch document consists of a front-to-back ordered list of
        DrawingObjects.  Each DrawingObject has the following properties:

            'position'      The position of the object within the document.
            'size'          The size of the object within the document.
            'penColour'     The colour to use for drawing the object's outline.
            'fillColour'    Colour to use for drawing object's interior.
            'lineSize'      Line width (in pixels) to use for object's outline.
            """

    # ==================
    # == Constructors ==
    # ==================

    def __init__(self, position=wx.Point(0, 0), size=wx.Size(0, 0),
                 penColour=wx.BLACK, fillColour=wx.WHITE, lineSize=1,
                 ):
        """ Standard constructor.

            The remaining parameters let you set various options for the newly
            created DrawingObject.
        """
        # One must take great care with constructed default arguments
        # like wx.Point(0,0) above.  *EVERY* caller that uses the
        # default will get the same instance.  Thus, below we make a
        # deep copy of those arguments with object defaults.

        self.position          = wx.Point(position.x,position.y)
        self.size              = wx.Size(size.x,size.y)
        self.penColour         = penColour
        self.fillColour        = fillColour
        self.lineSize          = lineSize

    # =============================
    # == Object Property Methods ==
    # =============================

    def getData(self):
        """ Return a copy of the object's internal data.

            This is used to save this DrawingObject to disk.
        """
        return [self.position.x, self.position.y,
                self.size.width, self.size.height,
                self.penColour.Red(),
                self.penColour.Green(),
                self.penColour.Blue(),
                self.fillColour.Red(),
                self.fillColour.Green(),
                self.fillColour.Blue(),
                self.lineSize]


    def setData(self, data):
        """ Set the object's internal data.

            'data' is a copy of the object's saved data, as returned by
            getData() above.  This is used to restore a previously saved
            DrawingObject.

            Returns an iterator to any remaining data not consumed by 
            this base class method.
        """
        #data = copy.deepcopy(data) # Needed?

        d = iter(data)
        try:
            self.position          = wx.Point(d.next(), d.next())
            self.size              = wx.Size(d.next(), d.next())
            self.penColour         = wx.Colour(red=d.next(),
                                              green=d.next(),
                                              blue=d.next())
            self.fillColour        = wx.Colour(red=d.next(),
                                              green=d.next(),
                                              blue=d.next())
            self.lineSize          = d.next()
        except StopIteration:
            raise ValueError('Not enough data in setData call')

        return d


    def hasPropertyEditor(self):
        return False

    def doPropertyEdit(self, parent):
        assert False, "Must be overridden if hasPropertyEditor returns True"

    def setPosition(self, position):
        """ Set the origin (top-left corner) for this DrawingObject.
        """
        self.position = position


    def getPosition(self):
        """ Return this DrawingObject's position.
        """
        return self.position


    def setSize(self, size):
        """ Set the size for this DrawingObject.
        """
        self.size = size


    def getSize(self):
        """ Return this DrawingObject's size.
        """
        return self.size


    def setPenColour(self, colour):
        """ Set the pen colour used for this DrawingObject.
        """
        self.penColour = colour


    def getPenColour(self):
        """ Return this DrawingObject's pen colour.
        """
        return self.penColour


    def setFillColour(self, colour):
        """ Set the fill colour used for this DrawingObject.
        """
        self.fillColour = colour


    def getFillColour(self):
        """ Return this DrawingObject's fill colour.
        """
        return self.fillColour


    def setLineSize(self, lineSize):
        """ Set the linesize used for this DrawingObject.
        """
        self.lineSize = lineSize


    def getLineSize(self):
        """ Return this DrawingObject's line size.
        """
        return self.lineSize


    # ============================
    # == Object Drawing Methods ==
    # ============================

    def draw(self, dc, selected):
        """ Draw this DrawingObject into our window.

            'dc' is the device context to use for drawing.  

            If 'selected' is True, the object is currently selected.
            Drawing objects can use this to change the way selected objects
            are drawn, however the actual drawing of selection handles
            should be done in the 'drawHandles' method
        """
        if self.lineSize == 0:
            dc.SetPen(wx.Pen(self.penColour, self.lineSize, wx.TRANSPARENT))
        else:
            dc.SetPen(wx.Pen(self.penColour, self.lineSize, wx.SOLID))
        dc.SetBrush(wx.Brush(self.fillColour, wx.SOLID))

        self._privateDraw(dc, self.position, selected)


    def drawHandles(self, dc):
        """Draw selection handles for this DrawingObject"""

        # Default is to draw selection handles at all four corners.
        dc.SetPen(wx.BLACK_PEN)
        dc.SetBrush(wx.BLACK_BRUSH)
        
        x,y = self.position
        self._drawSelHandle(dc, x, y)
        self._drawSelHandle(dc, x + self.size.width, y)
        self._drawSelHandle(dc, x, y + self.size.height)
        self._drawSelHandle(dc, x + self.size.width, y + self.size.height)


    # =======================
    # == Selection Methods ==
    # =======================

    def objectContainsPoint(self, x, y):
        """ Returns True iff this object contains the given point.

            This is used to determine if the user clicked on the object.
        """
        # Firstly, ignore any points outside of the object's bounds.

        if x < self.position.x: return False
        if x > self.position.x + self.size.x: return False
        if y < self.position.y: return False
        if y > self.position.y + self.size.y: return False

        # Now things get tricky.  There's no straightforward way of
        # knowing whether the point is within an arbitrary object's
        # bounds...to get around this, we draw the object into a
        # memory-based bitmap and see if the given point was drawn.
        # This could no doubt be done more efficiently by some tricky
        # maths, but this approach works and is simple enough.

        # Subclasses can implement smarter faster versions of this.

        bitmap = wx.EmptyBitmap(self.size.x + 10, self.size.y + 10)
        dc = wx.MemoryDC()
        dc.SelectObject(bitmap)
        dc.BeginDrawing()
        dc.SetBackground(wx.WHITE_BRUSH)
        dc.Clear()
        dc.SetPen(wx.Pen(wx.BLACK, self.lineSize + 5, wx.SOLID))
        dc.SetBrush(wx.BLACK_BRUSH)
        self._privateDraw(dc, wx.Point(5, 5), True)
        dc.EndDrawing()
        pixel = dc.GetPixel(x - self.position.x + 5, y - self.position.y + 5)
        if (pixel.Red() == 0) and (pixel.Green() == 0) and (pixel.Blue() == 0):
            return True
        else:
            return False

    handle_TOP    = 0
    handle_BOTTOM = 1
    handle_LEFT   = 0
    handle_RIGHT  = 1

    def getSelectionHandleContainingPoint(self, x, y):
        """ Return the selection handle containing the given point, if any.

            We return one of the predefined selection handle ID codes.
        """
        # Default implementation assumes selection handles at all four bbox corners.
        # Return a list so we can modify the contents later in moveHandle()
        if self._pointInSelRect(x, y, self.position.x, self.position.y):
            return [self.handle_TOP, self.handle_LEFT]
        elif self._pointInSelRect(x, y, self.position.x + self.size.width,
                                        self.position.y):
            return [self.handle_TOP, self.handle_RIGHT]
        elif self._pointInSelRect(x, y, self.position.x,
                                        self.position.y + self.size.height):
            return [self.handle_BOTTOM, self.handle_LEFT]
        elif self._pointInSelRect(x, y, self.position.x + self.size.width,
                                        self.position.y + self.size.height):
            return [self.handle_BOTTOM, self.handle_RIGHT]
        else:
            return None

    def moveHandle(self, handle, x, y):
        """ Move the specified selection handle to given canvas location.
        """
        assert handle is not None

        # Default implementation assumes selection handles at all four bbox corners.
        pt = wx.Point(x,y)
        x,y = self.position
        w,h = self.size
        if handle[0] == self.handle_TOP:
            if handle[1] == self.handle_LEFT:
                dpos = pt - self.position
                self.position = pt
                self.size.width -= dpos.x
                self.size.height -= dpos.y
            else:
                dx = pt.x - ( x + w )
                dy = pt.y - ( y )
                self.position.y = pt.y
                self.size.width += dx
                self.size.height -= dy
        else: # BOTTOM
            if handle[1] == self.handle_LEFT:
                dx = pt.x - ( x )
                dy = pt.y - ( y + h )
                self.position.x = pt.x
                self.size.width -= dx
                self.size.height += dy
            else: 
                dpos = pt - self.position
                dpos.x -= w
                dpos.y -= h
                self.size.width += dpos.x
                self.size.height += dpos.y


        # Finally, normalize so no negative widths or heights.
        # And update the handle variable accordingly.
        if self.size.height<0:
            self.position.y += self.size.height
            self.size.height = -self.size.height
            handle[0] = 1-handle[0]

        if self.size.width<0:
            self.position.x += self.size.width
            self.size.width = -self.size.width
            handle[1] = 1-handle[1]
            


    def finalizeHandle(self, handle, x, y):
        pass


    def objectWithinRect(self, x, y, width, height):
        """ Return True iff this object falls completely within the given rect.
        """
        if x          > self.position.x:                    return False
        if x + width  < self.position.x + self.size.width:  return False
        if y          > self.position.y:                    return False
        if y + height < self.position.y + self.size.height: return False
        return True

    # =====================
    # == Private Methods ==
    # =====================

    def _privateDraw(self, dc, position, selected):
        """ Private routine to draw this DrawingObject.

            'dc' is the device context to use for drawing, while 'position' is
            the position in which to draw the object.
        """
        pass

    def _drawSelHandle(self, dc, x, y):
        """ Draw a selection handle around this DrawingObject.

            'dc' is the device context to draw the selection handle within,
            while 'x' and 'y' are the coordinates to use for the centre of the
            selection handle.
        """
        dc.DrawRectangle(x - 3, y - 3, 6, 6)


    def _pointInSelRect(self, x, y, rX, rY):
        """ Return True iff (x, y) is within the selection handle at (rX, ry).
        """
        if   x < rX - 3: return False
        elif x > rX + 3: return False
        elif y < rY - 3: return False
        elif y > rY + 3: return False
        else:            return True

class LineDrawingObject(DrawingObject):
    """ DrawingObject subclass that represents one line segment.

        Adds the following members to the base DrawingObject:
            'startPt'       The point, relative to the object's position, where
                            the line starts.
            'endPt'         The point, relative to the object's position, where
                            the line ends.
            """

    def __init__(self, startPt=wx.Point(0,0), endPt=wx.Point(0,0), *varg, **kwarg):
        DrawingObject.__init__(self, *varg, **kwarg)

        self.startPt           = wx.Point(startPt.x,startPt.y)
        self.endPt             = wx.Point(endPt.x,endPt.y)

    # ============================
    # == Object Drawing Methods ==
    # ============================

    def drawHandles(self, dc):
        """Draw selection handles for this DrawingObject"""

        dc.SetPen(wx.BLACK_PEN)
        dc.SetBrush(wx.BLACK_BRUSH)

        x,y = self.position
        # Draw selection handles at the start and end points.
        self._drawSelHandle(dc, x + self.startPt.x, y + self.startPt.y)
        self._drawSelHandle(dc, x + self.endPt.x,   y + self.endPt.y)



    # =======================
    # == Selection Methods ==
    # =======================


    handle_START_POINT = 1
    handle_END_POINT = 2

    def getSelectionHandleContainingPoint(self, x, y):
        """ Return the selection handle containing the given point, if any.

            We return one of the predefined selection handle ID codes.
        """
        # We have selection handles at the start and end points.
        if self._pointInSelRect(x, y, self.position.x + self.startPt.x,
                                      self.position.y + self.startPt.y):
            return self.handle_START_POINT
        elif self._pointInSelRect(x, y, self.position.x + self.endPt.x,
                                        self.position.y + self.endPt.y):
            return self.handle_END_POINT
        else:
            return None
        
    def moveHandle(self, handle, x, y):
        """Move the handle to specified handle to the specified canvas coordinates
        """
        ptTrans = wx.Point(x-self.position.x, y-self.position.y)
        if handle == self.handle_START_POINT:
            self.startPt = ptTrans
        elif handle == self.handle_END_POINT:
            self.endPt = ptTrans
        else:
            raise ValueError("Bad handle type for a line")

        self._updateBoundingBox()

    # =============================
    # == Object Property Methods ==
    # =============================

    def getData(self):
        """ Return a copy of the object's internal data.

            This is used to save this DrawingObject to disk.
        """
        # get the basics
        data = DrawingObject.getData(self)
        # add our specifics
        data += [self.startPt.x, self.startPt.y,
                 self.endPt.x,   self.endPt.y]
        return data

    def setData(self, data):
        """ Set the object's internal data.

            'data' is a copy of the object's saved data, as returned by
            getData() above.  This is used to restore a previously saved
            DrawingObject.
        """
        #data = copy.deepcopy(data) # Needed?

        d = DrawingObject.setData(self, data)

        try:
            self.startPt           = wx.Point(d.next(), d.next())
            self.endPt             = wx.Point(d.next(), d.next())
        except StopIteration:
            raise ValueError('Not enough data in setData call')

        return d


    def setStartPt(self, startPt):
        """ Set the starting point for this line DrawingObject.
        """
        self.startPt = startPt - self.position
        self._updateBoundingBox()


    def getStartPt(self):
        """ Return the starting point for this line DrawingObject.
        """
        return self.startPt + self.position


    def setEndPt(self, endPt):
        """ Set the ending point for this line DrawingObject.
        """
        self.endPt = endPt - self.position
        self._updateBoundingBox()


    def getEndPt(self):
        """ Return the ending point for this line DrawingObject.
        """
        return self.endPt + self.position


    # =====================
    # == Private Methods ==
    # =====================


    def _privateDraw(self, dc, position, selected):
        """ Private routine to draw this DrawingObject.

            'dc' is the device context to use for drawing, while 'position' is
            the position in which to draw the object.  If 'selected' is True,
            the object is drawn with selection handles.  This private drawing
            routine assumes that the pen and brush have already been set by the
            caller.
        """
        dc.DrawLine(position.x + self.startPt.x,
                    position.y + self.startPt.y,
                    position.x + self.endPt.x,
                    position.y + self.endPt.y)
            
    def _updateBoundingBox(self):
        x = [self.startPt.x, self.endPt.x]; x.sort()
        y = [self.startPt.y, self.endPt.y]; y.sort()

        dp = wx.Point(-x[0],-y[0])
        self.position.x += x[0]
        self.position.y += y[0]
        self.size.width = x[1]-x[0]
        self.size.height = y[1]-y[0]

        self.startPt += dp
        self.endPt += dp
        
class ArrowDrawingObject(LineDrawingObject):

    def __init__(self, startPt=wx.Point(0,0), endPt=wx.Point(0,0), *varg, **kwarg):
        LineDrawingObject.__init__(self, startPt, endPt, *varg, **kwarg)
        
    def _privateDraw(self, dc, position, selected):
        line = ogl.LineShape()
        line.MakeLineControlPoints(2)
        line.SetEnds(position.x + self.startPt.x,
                     position.y + self.startPt.y,
                     position.x + self.endPt.x,
                     position.y + self.endPt.y)
        line.Initialise()
        line.SetPen(dc.GetPen())
        line.SetBrush(wx.Brush(dc.GetPen().GetColour(), wx.SOLID))
        line.AddArrow(ogl.ARROW_ARROW, size = dc.GetPen().GetWidth() * 4)
        line.Show(True)
        line.Draw(dc)

class ScribbleDrawingObject(DrawingObject):
    """ DrawingObject subclass that represents a poly-line or polygon
    """
    def __init__(self, points=[], *varg, **kwarg):
        DrawingObject.__init__(self, *varg, **kwarg)
        self.points = list(points)

    # =======================
    # == Selection Methods ==
    # =======================

    def addPoint(self, x,y):
        self.points.append((x-self.position.x,y-self.position.y))
        self._updateBoundingBox()

    def getPoint(self, idx):
        x,y = self.points[idx]
        return (x+self.position.x,y+self.position.y)

    def movePoint(self, idx, x,y):
        self.points[idx] = (x-self.position.x,y-self.position.y)
        self._updateBoundingBox()

    def popPoint(self, idx=-1):
        self.points.pop(idx)
        self._updateBoundingBox()


    # =============================
    # == Object Property Methods ==
    # =============================

    def getData(self):
        """ Return a copy of the object's internal data.

            This is used to save this DrawingObject to disk.
        """
        # get the basics
        data = DrawingObject.getData(self)
        # add our specifics
        data += [list(self.points)]

        return data


    def setData(self, data):
        """ Set the object's internal data.

            'data' is a copy of the object's saved data, as returned by
            getData() above.  This is used to restore a previously saved
            DrawingObject.
        """
        #data = copy.deepcopy(data) # Needed?
        d = DrawingObject.setData(self, data)

        try:
            self.points            = d.next()
        except StopIteration:
            raise ValueError('Not enough data in setData call')

        return d
        

    # =====================
    # == Private Methods ==
    # =====================
    def _privateDraw(self, dc, position, selected):
        """ Private routine to draw this DrawingObject.

            'dc' is the device context to use for drawing, while 'position' is
            the position in which to draw the object.  If 'selected' is True,
            the object is drawn with selection handles.  This private drawing
            routine assumes that the pen and brush have already been set by the
            caller.
        """
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.DrawLines(self.points, position.x, position.y)

    def _updateBoundingBox(self):
        x = min([p[0] for p in self.points])
        y = min([p[1] for p in self.points])
        x2 = max([p[0] for p in self.points])
        y2 = max([p[1] for p in self.points])
        dx = -x
        dy = -y
        self.position = wx.Point(self.position.x + x,self.position.y + y)
        self.size = wx.Size(x2-x, y2-y)
        #self.position.x += x
        #self.position.y += y
        #self.size.width = x2-x
        #self.size.height = y2-y
        # update coords also because they're relative to self.position
        for i,p in enumerate(self.points):
            self.points[i] = (p[0]+dx,p[1]+dy)

class BlurDrawingObject(ScribbleDrawingObject):
    def __init__(self, blurredbmp, offset, *varg, **kwarg):
        ScribbleDrawingObject.__init__(self, *varg, **kwarg)
        # self.pen = wx.Pen('white', self.lineSize, wx.STIPPLE_MASK_OPAQUE)
        # self.pen.SetStipple(blurredbmp)
        self.bmp = blurredbmp
        self.offset = offset
        
    def _privateDraw(self, dc, position, selected):
        # dc.SetPen(self.pen)
        # ScribbleDrawingObject._privateDraw(self, dc, position, selected)
        srcdc = wx.MemoryDC()
        srcdc.SelectObject(self.bmp)
        for point in self.points:
            dstpt = (point[0] + position.x - self.lineSize/2,
                     point[1] + position.y - self.lineSize/2)
            srcpt = (dstpt[0] - self.offset[0],
                     dstpt[1] - self.offset[1])
            dc.BlitPointSize(dstpt, (self.lineSize, self.lineSize), srcdc, srcpt)
        srcdc.SelectObject(wx.NullBitmap)
        del srcdc
        
class RectDrawingObject(DrawingObject):
    """ DrawingObject subclass that represents an axis-aligned rectangle.
    """
    def __init__(self, *varg, **kwarg):
        DrawingObject.__init__(self, *varg, **kwarg)

    def objectContainsPoint(self, x, y):
        """ Returns True iff this object contains the given point.

            This is used to determine if the user clicked on the object.
        """
        # Firstly, ignore any points outside of the object's bounds.

        if x < self.position.x: return False
        if x > self.position.x + self.size.x: return False
        if y < self.position.y: return False
        if y > self.position.y + self.size.y: return False

        # Rectangles are easy -- they're always selected if the
        # point is within their bounds.
        return True

    # =====================
    # == Private Methods ==
    # =====================

    def _privateDraw(self, dc, position, selected):
        """ Private routine to draw this DrawingObject.

            'dc' is the device context to use for drawing, while 'position' is
            the position in which to draw the object.  If 'selected' is True,
            the object is drawn with selection handles.  This private drawing
            routine assumes that the pen and brush have already been set by the
            caller.
        """
        dc.DrawRectangle(position.x, position.y,
                         self.size.width, self.size.height)

class EllipseDrawingObject(DrawingObject):
    """ DrawingObject subclass that represents an axis-aligned ellipse.
    """
    def __init__(self, *varg, **kwarg):
        DrawingObject.__init__(self, *varg, **kwarg)

    # =====================
    # == Private Methods ==
    # =====================
    def _privateDraw(self, dc, position, selected):
        """ Private routine to draw this DrawingObject.

            'dc' is the device context to use for drawing, while 'position' is
            the position in which to draw the object.  If 'selected' is True,
            the object is drawn with selection handles.  This private drawing
            routine assumes that the pen and brush have already been set by the
            caller.
        """
        dc.DrawEllipse(position.x, position.y,
                       self.size.width, self.size.height)

